#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Filename: export_as_excel_order.py
# Author: ChenChangSong
# Date: 2025-08-24

"""
功能描述: 导出为Excel，正序
"""
import sys
import os
import re
import json
import xmindparser
import time
import random
from itertools import islice
from openpyxl import Workbook
from openpyxl.worksheet.datavalidation import DataValidation
from openpyxl.formatting.rule import CellIsRule
from openpyxl.styles import PatternFill, Alignment

# 不再添加项目路径到环境
from comm.common_code import get_excel_data, logger, log_to_ui_info, log_to_ui_error


from PyQt5.QtWidgets import QMessageBox

MIN_REQUEST_DELAY = 1.0
MAX_REQUEST_DELAY = 2.0

gd_messages = "提示：导出过程可能会出现【未响应】，不要关闭软件，这是正常的！后台一直在导出用例，导出完成后会恢复正常！"

def export_to_excel_order(xmind_path, level=0):
    results = check_xmind_file_and_case_list(xmind_path, level=level)
    if results is None:  # 用户取消操作
        return None
    if results:
        files_list = test_case_write_to_excel(xmind_path, results)
        return files_list
    else:
        print("没有提取到有效的测试用例数据")
        return []

def check_xmind_file_and_case_list(xmind_path, level=0):
    """处理XMind文件，提取需求ID和一级节点标题，并检查是否继续,如果继续,则返测试用例列表"""
    excel_data = get_excel_data()
    # 初始化需求ID
    xmind_requirement_id = None

    log_to_ui_info(gd_messages)# 添加提示

    try:
        # 处理XMind文件
        xmind_content = xmindparser.xmind_to_dict(xmind_path)
    except Exception as e:
        logger.error(f"处理XMind文件时发生错误: {e}")
        log_to_ui_error(f"处理XMind文件时发生错误: {e}")
        return None, None

    # 提取需求ID - 改进的逻辑
    json_str = xmind_content[0]['topic']['title']
    
    # 尝试多种格式提取需求ID
    patterns = [
        r'需求ID[：:]\s*(\d+)',
        r'需求id[：:]\s*(\d+)',
        r'requirement_id[：:]\s*(\d+)'
    ]
    
    for pattern in patterns:
        match = re.search(pattern, json_str, re.IGNORECASE)
        if match:
            xmind_requirement_id = match.group(1)
            logger.info(f'提取到需求ID: {xmind_requirement_id}')
            log_to_ui_info(f'提取到需求ID: {xmind_requirement_id}')
            break
    
    # 如果没有找到，尝试解析JSON
    if not xmind_requirement_id and ('{' in json_str and '}' in json_str):
        try:
            data = json.loads(json_str)
            xmind_requirement_id = data.get('requirement_id') or data.get('需求ID')
            if xmind_requirement_id:
                logger.info(f'从JSON提取到需求ID: {xmind_requirement_id}')
                log_to_ui_info(f'从JSON提取到需求ID: {xmind_requirement_id}')
        except json.JSONDecodeError:
            logger.error('无法解析JSON格式的需求ID')
            log_to_ui_error('无法解析JSON格式的需求ID')

    # 获取一级目录检查配置
    first_level_check = excel_data.get('一级目录检查', '')
    if first_level_check:
        first_level_check = [item.strip() for item in first_level_check.split('、')]
    else:
        first_level_check = []
    logger.info(f'一级目录检查配置: {first_level_check}')
    log_to_ui_info(f'一级目录检查配置: {first_level_check}')

    if not xmind_content or not xmind_content[0].get('topic'):
        logger.error('XMind文件格式错误，无法提取需求ID')
        log_to_ui_error('XMind文件格式错误，无法提取需求ID')
        return None, None

    # 提取所有一级节点标题
    try:
        titles = []
        if xmind_content[0]['topic'].get('topics'):
            titles = [module['title'] for module in xmind_content[0]['topic']['topics']]
        logger.info(f'XMind文件中所有第一个节点标题: {titles}')
        log_to_ui_info(f'XMind文件中所有第一个节点标题: {titles}')
        
    except Exception as e:
        titles = []
        logger.error(f'获取节点标题时发生错误: {str(e)}')
        log_to_ui_error(f'获取节点标题时发生错误: {str(e)}')



    # 如果需求ID不为空且格式正确，且所有一级节点标题都在检查配置中，则弹窗确认
    if (xmind_requirement_id and len(xmind_requirement_id) == 7 and 
        first_level_check and all(title in first_level_check for title in titles)):
        reply = QMessageBox.question(None, "格式正确，确认是否导出", 
                                    "您填写的需求ID和一级目录已符合要求!\n\n" + gd_messages + "\n\n是否确认导出？",
                                    QMessageBox.Yes | QMessageBox.No, 
                                    QMessageBox.Yes)

        if reply == QMessageBox.No:
            logger.info('用户取消导出')
            log_to_ui_info('用户取消导出')
            return None  # 返回None表示用户取消
        logger.info('用户确认导出')
        log_to_ui_info('用户确认导出')
        log_to_ui_info(f"导出过程可能会出现【未响应】，不要关闭软件，后台一直在导出用例，这是正常的！导出完成后会恢复正常！")

    # 如果需求ID为空，或XMind文件中任何一个第一个节点标题不在一级目录检查配置中，则需要确认是否继续操作
    elif not xmind_requirement_id or len(xmind_requirement_id) != 7 or (first_level_check and any(title not in first_level_check for title in titles)):
        # 构建提示消息
        error_messages = []

        # 检查需求ID问题
        if not xmind_requirement_id or len(xmind_requirement_id) != 7:
            req_id_display = str(xmind_requirement_id) if xmind_requirement_id else "空"
            error_messages.append(f"检测到需求ID为：{req_id_display}，存在错误(需求ID格式应为7位数字，否则会关联需求失败)")
        
        # 检查目录问题
        if first_level_check and any(title not in first_level_check for title in titles):
            error_messages.append(f"第一个节点目录:{titles}中存在不在{first_level_check}中的目录，请按产品功能路径修改脑图目录！")
        
        # 根据问题类型提供不同的提示
        if len(error_messages) == 2:
            # 需求和目录都有问题
            message = "1、" + error_messages[0] + "\n\n2、" + error_messages[1] + "\n\n" + gd_messages + "\n\n是否继续导出？"
        elif not xmind_requirement_id or len(xmind_requirement_id) != 7:
            # 只有需求ID有问题
            message = error_messages[0] + "\n\n" + gd_messages + "\n\n是否继续导出？"
        else:
            # 只有目录有问题
            message = error_messages[0] + "\n\n" + gd_messages + "\n\n是否继续导出？"

        reply = QMessageBox.question(None, "格式错误，确认是否导出", 
                                    message,
                                    QMessageBox.Yes | QMessageBox.No, 
                                    QMessageBox.No)

        if reply == QMessageBox.No:
            logger.info('用户取消操作')
            log_to_ui_info('用户取消操作')
            return None  # 返回None表示用户取消
        logger.info('用户确认继续操作')
        log_to_ui_info('用户确认继续操作')

                    

    results = []  # 存储提取结果
    def traverse(node, current_path, module_name):
        """递归遍历xmind节点"""
        if 'topics' in node:
            for topic in node['topics']:
                new_path = current_path + [topic['title']]
                if 'topics' in topic:
                    traverse(topic, new_path, module_name)
                else:
                    # 提取叶子节点作为测试用例[需求ID,用例目录,用例名称,用例步骤,预期结果]
                    if level >= 3 and len(new_path) > level + 1:
                        case_directory = module_name + "-" + "-".join(new_path[:level-1])
                    elif len(new_path) == 2 or len(new_path) == 1:
                        case_directory = module_name
                    else:
                        case_directory = module_name + "-" + "-".join(new_path[:-2])
                    case_name = new_path[-2] + "-" + new_path[-1]
                    case_steps = module_name + " >> " + " >> ".join(new_path[:-1])
                    one_case = [xmind_requirement_id, case_directory, case_name, case_steps, new_path[-1]]
                    logger.info(f'提取到用例:\n {one_case}')
                    results.append(one_case)
    
    # 处理每个模块
    for module in xmind_content[0]['topic']['topics']:
        module_name = module['title']
        if 'topics' in module:
            for func in module['topics']:
                traverse(func, [func['title']], module_name)

    logger.info(f'提取到用例列表: {results}')
    log_to_ui_info(f'提取到用例列表: {results}')
    logger.info(f'提取到用例总数: {len(results)}')
    log_to_ui_info(f'提取到用例总数: {len(results)}')
    return results

def test_case_write_to_excel(xmind_path, row_data_list):
    """将测试用例数据写入Excel文件
    :param xmind_path: 原始xmind文件路径（用于生成输出文件名）
    :param row_data_list: 测试用例数据列表
    """
    # ============== 文件准备 ==============
    file_dir, file_name = os.path.split(xmind_path)
    file_base = os.path.splitext(file_name)[0]
    timestamp = time.strftime("%Y%m%d_%H%M%S")

    # 计算需要拆分的文件数量 (每500条数据一个文件)
    total_cases = len(row_data_list)
    # 修复文件数量计算逻辑
    num_files = (total_cases - 1) // 500 + 1  # 正确的向上取整方法

    print(f"总用例数: {total_cases}, 需要拆分成 {num_files} 个文件")  # 调试信息

    # 文件列表
    files_list = []
    # ============== 处理每个文件分片 ==============
    for file_index in range(num_files):
        # 计算当前分片的数据范围
        start_index = file_index * 500
        end_index = min((file_index + 1) * 500, total_cases)
        current_data = row_data_list[start_index:end_index]

        print(f"处理分片 {file_index + 1}: {len(current_data)}条数据")  # 调试信息

        wb = Workbook()
        ws = wb.active
        ws.title = "测试用例"  # 设置工作表名称

        # ============== 表头设置 ==============
        title_list = ['需求ID', '用例目录', '用例名称', '用例步骤', '预期结果', '执行结果', '备注']
        ws.append(title_list)
        ws.auto_filter.ref = "A1:G1"  # 设置自动筛选

        # ============== 列宽设置 ==============
        col_config = {
            'A': 8, 'B': 40, 'C': 25,
            'D': 80, 'E': 20, 'F': 10, 'G': 25
        }
        for col, width in col_config.items():
            ws.column_dimensions[col].width = width

        # ============== 数据验证设置 ==============
        status_options = ["通过", "失败", "阻塞", "未执行", "待确认", "用例待更新", "用例废弃"]
        dv = DataValidation(
            type="list",
            formula1=f'"{",".join(status_options)}"',
            allow_blank=True
        )
        ws.add_data_validation(dv)
        # 应用范围根据当前分片数据量计算
        if current_data:  # 确保有数据时才添加验证
            dv_range = f"F2:F{len(current_data) + 1}"
            dv.add(dv_range)

        # ============== 条件格式设置 ==============
        status_colors = {
            "通过": '00FF00',  # 绿色
            "失败": 'FF7F00',  # 橙色
            "阻塞": 'FF0000',  # 红色
            "未执行": 'FFFF00',  # 黄色
            "待确认": 'FFFF00',
            "用例待更新": 'FFFF00',
            "用例废弃": 'FFFF00'
        }
        if current_data:  # 确保有数据时才添加格式
            for status, color in status_colors.items():
                rule = CellIsRule(
                    operator='equal',
                    formula=[f'"{status}"'],
                    fill=PatternFill(
                        start_color=color,
                        end_color=color,
                        fill_type='solid'
                    )
                )
                # 应用范围根据当前分片数据量计算
                cf_range = f'F2:F{len(current_data) + 1}'
                ws.conditional_formatting.add(cf_range, rule)

        # ============== 写入测试数据 ==============
        for case_data in current_data:
            ws.append(case_data)

        # ============== 格式优化 ==============
        ws.freeze_panes = 'A2'  # 冻结首行

        # 设置所有单元格自动换行
        for row in ws.iter_rows(min_row=1):
            for cell in row:
                cell.alignment = Alignment(wrap_text=True, vertical='top')

        # 设置行高
        ws.row_dimensions[1].height = 20  # 标题行
        for row_idx in range(2, len(current_data) + 2):
            ws.row_dimensions[row_idx].height = 32  # 数据行

        # ============== 文件保存 ==============
        # 添加分片序号到文件名（当有多个文件时）
        if num_files > 1:
            suffix = f"_正序_{timestamp}_{file_index + 1}"
        else:
            suffix = f"_正序_{timestamp}"

        output_path = os.path.join(file_dir, f"{file_base}{suffix}.xlsx")

        # 确保输出目录存在
        os.makedirs(file_dir, exist_ok=True)

        try:
            wb.save(output_path)
            print(f"成功生成文件: {output_path} ({len(current_data)}条用例)")
            files_list.append(output_path)
        except PermissionError:
            print(f"权限错误: 无法写入文件 {output_path}，可能文件正在被其他程序打开")
        except Exception as e:
            print(f"保存文件 {output_path} 时出错: {str(e)}")

    print(f"总计生成 {num_files} 个文件，共处理 {total_cases} 条测试用例")
    return files_list


if __name__ == '__main__':
    # 获取示例文件路径
    from ui_code.common_ui import get_project_root
    xmind_path = get_project_root() / "files" / "【示例】商品详情qwer12345.xmind"
    
    # 执行所有方法
    export_to_excel_order(str(xmind_path), level=4)
